package com.cmcc.pcap.packet.udp.quic.xdr;

import at.favre.lib.crypto.HKDF;
import com.cmcc.packet.entity.*;
import com.cmcc.pcap.packet.udp.UdpConnection;
import com.cmcc.pcap.packet.util.DateUtil;
import com.cmcc.pcap.packet.util.PacketAnalysisConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.tls.TlsUtils;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLHandshakeException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class QuicXdrGenerate {

	private static Logger logger = LogManager.getLogger(QuicXdrGenerate.class);

	public List<QuicEntity> generate(UdpConnection udpConnection, String taskId) {
		List<QuicEntity> quicEntityList = new ArrayList<QuicEntity>();
		logger.debug("this is quic xdr generate !");
		QuicEntity quicEntity = new QuicEntity();
		quicEntity.setTaskId(Integer.parseInt(taskId));
		quicEntity.setType("QUIC");

		try {
			saveUdpConnectionToEntityList(udpConnection,quicEntity);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			logger.error(e.getCause(), e);
		}

		//将每个XdrEntity加入XdrEntityList中，调用SaveHttpTransactionInfo的save方法保存到数据库中
		quicEntityList.add(quicEntity);

		/**
		 * Interface
		 * ProcedureStartTime
		 * ProcedureEndTime
		 * ProtocolType
		 * AppType
		 * AppSubType
		 * IPAddressType
		 * UserIPv4
		 * UserIPv6
		 * ......
		 *
		 *
		 */
		return quicEntityList;
	}

	public void saveUdpConnectionToEntityList(UdpConnection udpConnection,QuicEntity quicEntity) throws ParseException{

		if(udpConnection.al == null || udpConnection.al.size() == 0){
			return;
		}

		// 判断是否为initail 报文 通过longheader格式


		// 找到连接的connectionKey直接填写源地址和目的地址
		String[] connectionKey = udpConnection.connectionKey.split("-");
		String[] usersocket = connectionKey[0].split(":");
		String[] serversocket = connectionKey[1].split(":");

		String userIpv4;
		String userIpv6;
		Integer userPort;

		String appServerIpIpv4;
		String appServerIpIpv6;
		Integer appServerPort;

		if(usersocket.length>2) {
			//填写终端用户的ipv6地址和端口
			String[] useriplist = Arrays.copyOf(usersocket, usersocket.length-1);
			userIpv6 = String.join(":", useriplist);
			userPort =  Integer.valueOf(usersocket[8]);

			quicEntity.setUserIpv6(userIpv6);
			quicEntity.setUserPort(userPort);

			//填写ip_address_type,ipv4是0,ipv6是1。
			quicEntity.setIpAddressType(1);
		}
		else {
			//填写终端用户的ipv4地址和端口
			userIpv4 = usersocket[0];
			userIpv4 = userIpv4.replace("/", "");
			userPort = Integer.valueOf(usersocket[1]);

			quicEntity.setUserIpv4(userIpv4);
			quicEntity.setUserPort(userPort);

			//填写ip_address_type,ipv4是0,ipv6是1。
			quicEntity.setIpAddressType(0);
		}

		// 填写L4 Protocol,0为tcp,1为udp
		quicEntity.setL4Protocal(1);

		if(serversocket.length>2) {
			//填写服务端用户的ipv6地址和端口
			String[] appserveriplist = Arrays.copyOf(serversocket, serversocket.length-1);
			appServerIpIpv6 = String.join(":", appserveriplist);
			appServerPort =  Integer.valueOf(serversocket[8]);

			quicEntity.setAppServerIpIpv6(appServerIpIpv6);
			quicEntity.setAppServerPort(appServerPort);
		}
		else {
			//填写服务端用户的ipv4地址和端口
			appServerIpIpv4 = serversocket[0];
			appServerIpIpv4 = appServerIpIpv4.replace("/", "");
			appServerPort = Integer.valueOf(serversocket[1]);

			quicEntity.setAppServerIpIpv4(appServerIpIpv4);
			quicEntity.setAppServerPort(appServerPort);
		}

		Integer length = udpConnection.al.size();
		logger.debug("tcpConnection length:"+length);

		//计算procedure_start_time
		long timestamp_procedure_start_time = 0l;
		Date procedureStartTime = DateUtil.formatTimestamp(timestamp_procedure_start_time);
		quicEntity.setProcedureStartTime(procedureStartTime);


		//计算procedure_end_time
		long timestamp_procedure_end_time = 0l;
		Date procedureEndTime = DateUtil.formatTimestamp(timestamp_procedure_end_time);
		quicEntity.setProcedureEndTime(procedureEndTime);

		//填写interface
		Integer interFace = 11;
		quicEntity.setInterfaceCode(interFace);

		//填写payload
		//控制拿到payload的报文个数
		/*
		 * 从配置文件中拿payload个数
		 */
		Integer payloadNum = PacketAnalysisConfig.getConfigAsInt("tcp.payloads.num");
		byte[] payloads = new byte[]{};

		/*
		 * 根据配置文件，控制拿payload的个数
		 */
		Integer getPayloadLength = 0;
		if(payloadNum < udpConnection.al.size())
			getPayloadLength = payloadNum;
		else
			getPayloadLength = udpConnection.al.size();

		for(int i=0; i<getPayloadLength; i++) {
			byte[] temp = udpConnection.al.get(i).udpPacket.getPayload().getRawData();
			byte[] c = new byte[payloads.length+temp.length];
			System.arraycopy(payloads, 0, c, 0, payloads.length);
			System.arraycopy(temp, 0, c, payloads.length, temp.length);
			payloads = c;
		}

		// 解析有效载荷
		ByteBuffer data = ByteBuffer.wrap(payloads);

		if (data.position() != 0){
			data.position(0);
		}

		// 确保报文至少有1个字节
		if (data.remaining() < 1 +  4 +     1 +      0 +  1 +      0 +  1 +    1 +    1) {
			throw new ParseException("Packet too short: " + data.remaining() + " bytes", 0);
		}

		/*UDP的数据部分为QUIC报文
		  flags为quic报文的第一个字节
		  当前是initial报文
		 */
		byte flags = data.get();
		if ( (flags & 0x30) >> 4 == 0){
			// initial报文
			int version = data.getInt();

		    if (version == 1){
				// 仅解析version1
				int dstConnIdLength = data.get();
				if (dstConnIdLength < 0 || dstConnIdLength > 20) {
					throw new ParseException("Invalid destination connection ID length: " + dstConnIdLength, 0);
				}
				if (data.remaining() < dstConnIdLength) {
					throw new ParseException("Packet too short: " + data.remaining() + " bytes", 0);
				}
				// destinationConnectionId
				byte[] destinationConnectionId = new byte[dstConnIdLength];
				data.get(destinationConnectionId);

				int srcConnIdLength = data.get();
				if (srcConnIdLength < 0 || srcConnIdLength > 20) {
					throw new ParseException("Invalid source connection ID length: " + srcConnIdLength, 0);
				}
				if (data.remaining() < srcConnIdLength) {
					throw new ParseException("Packet too short: " + data.remaining() + " bytes", 0);
				}
				// sourceConnectionId
				byte[] sourceConnectionId = new byte[srcConnIdLength];
				data.get(sourceConnectionId);

				// 解析token
				long tokenLength = parseLong(data);
				if (tokenLength > 0) {
					if (tokenLength <= data.remaining()) {
						byte[] token = new byte[(int) tokenLength];
						data.get(token);
					}
					else {
						throw new ParseException("Packet too short: " + data.remaining() + " bytes", 0);
					}
				}

				//key中包含了需要的部分数据
				Map<String, byte[]> key = new HashMap<>();
				key.put("version", ByteBuffer.allocate(4).putInt(version).array());
				key.put("dcid", destinationConnectionId);

				// 客户端inital密钥
				byte[] initialSecret = computeInitialSecret(key);
				String hex = new BigInteger(1, initialSecret).toString(16);
				// 计算客户端inital密钥
				byte[] initialNodeSecret = hkdfExpandLabel(initialSecret, "client in", "", (short) 32);
				Map<String,byte[]> ckeys = computeKeys(initialNodeSecret);

				//解析剩余的 PacketNumber 和 有效载荷 payload
				int payloadlength;
				payloadlength = parse(data);

				// 获取packetNumber
				long largestPacketNumber = 0L;
				try {
					List<QuicFrame> frames = parsePacketNumberAndPayload(data, flags, payloadlength, ckeys, largestPacketNumber);

					List<CryptoFrame> cryptos = new ArrayList<>();
					if(frames.size() != 0){
						//解析成功，筛选所有CryptoData frames
						for(QuicFrame frame : frames){
							if (frame instanceof CryptoFrame){
								CryptoFrame crypto = ((CryptoFrame) frame);
								cryptos.add(crypto);
							}
						}
					}

					// 排序所有cryptos，并拼接其中的
					byte[] cryptoBytes = cryptos.stream().sorted(
							Comparator.comparing(CryptoFrame::getOffset)
					).map(CryptoFrame::getCryptoData).reduce(new byte[0], (a, b) -> {
						byte[] c = new byte[a.length + b.length];
						System.arraycopy(a, 0, c, 0, a.length);
						System.arraycopy(b, 0, c, a.length, b.length);
						return c;
					});

					ByteBuffer clientBuffer = ByteBuffer.wrap(cryptoBytes);
					String serverName = ParseClientHello(clientBuffer);

					quicEntity.setServerName(serverName);
				} catch (ParseException e){
					e.printStackTrace();
				}
			}
		}else{
			// 非initial报文
			return;
		}
	}

	public enum CipherSuite {
		TLS_AES_128_GCM_SHA256(0x1301),
		TLS_AES_256_GCM_SHA384(0x1302),
		TLS_CHACHA20_POLY1305_SHA256(0x1303),
		TLS_AES_128_CCM_SHA256(0x1304),
		TLS_AES_128_CCM_8_SHA256(0x1305);

		public final short value;

		CipherSuite(int value) {
			this.value = (short) value;
		}
	}

	private String ParseClientHello(ByteBuffer clientHello) throws  ParseException{
		String serverName = "";

		// 仅处理Client Hello
		int startPos = clientHello.position();

		if (clientHello.remaining() < 4) {
			throw new ParseException("message underflow", 0);
		}
		if (clientHello.remaining() < 1 + 3 + 2 + 32 + 1 + 2 + 2 + 2 + 2) {
			throw new ParseException("message underflow", 0);
		}

		int messageType = clientHello.get();
		if (messageType != 1) {
			throw new ParseException("message type error", 0);
		}

		int length = ((clientHello.get() & 0xff) << 16) | ((clientHello.get() & 0xff) << 8) | (clientHello.get() & 0xff);
		if (clientHello.remaining() < length) {
			throw new ParseException("message underflow", 0);
		}

		int legacyVersion = clientHello.getShort();
		if (legacyVersion != 0x0303) {
			throw new ParseException("legacy version must be 0303", 0);
		}

		byte[] clientRandom = new byte[32];
		clientHello.get(clientRandom);

		int sessionIdLength = clientHello.get();
		if (sessionIdLength > 0) {
			clientHello.get(new byte[sessionIdLength]);
		}

		List<CipherSuite> cipherSuites = new ArrayList<>();
		int cipherSuitesLength = clientHello.getShort();
		for (int i = 0; i < cipherSuitesLength; i += 2) {
			int cipherSuiteValue = clientHello.getShort();
			Arrays.stream(CipherSuite.values())
					.filter(item -> item.value == cipherSuiteValue)
					.findFirst()
					.ifPresent(item -> cipherSuites.add(item));
		}

		int legacyCompressionMethodsLength = clientHello.get();
		int legacyCompressionMethod = clientHello.get();
		if (legacyCompressionMethodsLength != 1 || legacyCompressionMethod != 0) {
			throw new ParseException("Invalid legacy compression method", 0);
		}

		if (clientHello.remaining() < 2) {
			throw new ParseException("Extension field must be at least 2 bytes long", 0);
		}
		int remainingExtensionsLength = clientHello.getShort() & 0xffff;
		if (clientHello.remaining() < remainingExtensionsLength) {
			throw new ParseException("Extensions too short", 0);
		}

		while (remainingExtensionsLength >= 4) {
			clientHello.mark();
			int extensionType = clientHello.getShort() & 0xffff;
			int extensionLength = clientHello.getShort() & 0xffff;
			remainingExtensionsLength -= 4;

			if (extensionLength > remainingExtensionsLength) {
				throw new ParseException("Extension length exceeds extensions length", 0);
			}
			if (extensionType != 0){
				byte[] tmp = new byte[extensionLength];
				clientHello.get(tmp);
				remainingExtensionsLength -= extensionLength;
				continue;
			}

			// 类型是否是server_name
			if (extensionType == 0) {
				if (clientHello.limit() - clientHello.position() < 4) {
					throw new ParseException("extension underflow", 0);
				}
				int serverNameListLength = clientHello.getShort() & 0xffff;

				serverName = parseServerName(clientHello);
				break;
			}
			remainingExtensionsLength -= extensionLength;
		}

		return serverName;
	}

	private int parseExtensionHeader(ByteBuffer clientHello) {
		int extensionType = clientHello.getShort() & 0xffff;
		int extensionDataLength = clientHello.getShort() & 0xffff;
	    return extensionDataLength;

	}

	private String parseServerName(ByteBuffer buffer) throws ParseException{
		int nameType = buffer.get();
		switch (nameType) {
			case 0:
				// host_name
				int hostNameLength = buffer.getShort() & 0xffff;
				if (hostNameLength > buffer.remaining()) {
					throw new ParseException("extension underflow", 0);
				}
				byte[] hostNameBytes = new byte[hostNameLength];
				buffer.get(hostNameBytes);
				// "The hostname is represented as a byte string using ASCII encoding without a trailing dot. "
				return new String(hostNameBytes, Charset.forName("ASCII"));
		}
		// unsupported type, RFC 6066 only defines hostname
		throw new ParseException("invalid NameType", 0);
	}

	private int parseCompressionMethodsLength(ByteBuffer clientHello) {
		int compressionMethodsLength = clientHello.get();
		return compressionMethodsLength;
	}

	private int parseCipherSuitesLength(ByteBuffer clientHello) {
		int cipherSuitesLength = clientHello.getShort();
		return cipherSuitesLength;
	}

	private int parseSessionIdLength(ByteBuffer clientHello) {
		int sessionIdLength = clientHello.get();
		return sessionIdLength;
	}

	private int parseVersion(ByteBuffer clientHello) {
		int version = clientHello.getShort();
		return version;
	}

	private byte[] computeInitialSecret(Map<String,byte[]> headinfo){
		byte[] version = headinfo.get("version");
		int versionInt = new Integer(version[0] & 0xff) * 256 * 256 * 256 + new Integer(version[1] & 0xff) * 256 * 256 + new Integer(version[2] & 0xff) * 256 + new Integer(version[3] & 0xff);
		byte[] destinationConnectId = headinfo.get("dcid");
		// 初始化screte 计算器
		HKDF hkdf = HKDF.fromHmacSha256();
		byte[] STATIC_SALT_V1 = new byte[] {
				(byte) 0x38, (byte) 0x76, (byte) 0x2c, (byte) 0xf7, (byte) 0xf5, (byte) 0x59, (byte) 0x34, (byte) 0xb3,
				(byte) 0x4d, (byte) 0x17, (byte) 0x9a, (byte) 0xe6, (byte) 0xa4, (byte) 0xc8, (byte) 0x0c, (byte) 0xad,
				(byte) 0xcc, (byte) 0xbb, (byte) 0x7f, (byte) 0x0a };

		byte[] STATIC_SALT_V2 = new byte[] {
				(byte) 0x0d, (byte) 0xed, (byte) 0xe3, (byte) 0xde, (byte) 0xf7, (byte) 0x00, (byte) 0xa6, (byte) 0xdb,
				(byte) 0x81, (byte) 0x93, (byte) 0x81, (byte) 0xbe, (byte) 0x6e, (byte) 0x26, (byte) 0x9d, (byte) 0xcb,
				(byte) 0xf9, (byte) 0xbd, (byte) 0x2e, (byte) 0xd9 };

//		byte[] STATIC_SALT_DRAFT_29 = new byte[] {
//				(byte) 0xaf, (byte) 0xbf, (byte) 0xec, (byte) 0x28, (byte) 0x99, (byte) 0x93, (byte) 0xd2, (byte) 0x4c,
//				(byte) 0x9e, (byte) 0x97, (byte) 0x86, (byte) 0xf1, (byte) 0x9c, (byte) 0x61, (byte) 0x11, (byte) 0xe0,
//				(byte) 0x43, (byte) 0x90, (byte) 0xa8, (byte) 0x99 };

		byte[] initialSalt = versionInt == 1 ? STATIC_SALT_V1 : STATIC_SALT_V2;
		return hkdf.extract(initialSalt, destinationConnectId);
	}

	private int getTokenLengthInLongHeader(byte[] left) {
		int token = 0;
		byte firstByte = left[0];  // 取一个字节
		left = Arrays.copyOfRange(left, 1, left.length);
		Integer token_len = 1 << ((firstByte & 0xc0) >> 6);
		Integer b1 = firstByte & (0xff - 0xc0);

		// 如果长度为1， 只截取长度为1的token
		if (token_len == 1) {
			token = Integer.parseUnsignedInt(b1.toString());
			return token;
		}

		byte b2 = left[0];
		left = Arrays.copyOfRange(left, 1, left.length);
		if (token_len == 2) {
			token = b2 << 0 + b1 << 8;
			return token;
		}

		byte b3 = left[0];
		left = Arrays.copyOfRange(left, 1, left.length);

		byte b4 = left[0];
		left = Arrays.copyOfRange(left, 1, left.length);
		if (token_len == 4){
			token = b4 << 0 + b3 << 8 + b2 << 16 + b1 << 24;
			return token;
		}

		byte b5 = left[0];
		left = Arrays.copyOfRange(left, 1, left.length);

		byte b6 = left[0];
		left = Arrays.copyOfRange(left, 1, left.length);

		byte b7 = left[0];
		left = Arrays.copyOfRange(left, 1, left.length);

		byte b8 = left[0];
		left = Arrays.copyOfRange(left, 1, left.length);

		return b8 << 0 + b7 << 8 + b6 << 16 + b5 << 24 + b4 << 32 + b3 << 40 + b2 << 48 + b1 << 56;
	}

	private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
	private static final int KEY_LENGTH = 16;
	private static final int IV_LENGTH = 12;
	private static final int TAG_LENGTH = 16;

	public static long parseLong(ByteBuffer buffer) throws ParseException {
		if (buffer.remaining() < 1) {
			throw new ParseException("Not enough bytes to parse long", 0);
		}

		long value;
		byte firstLengthByte = buffer.get();
		switch ((firstLengthByte & 0xc0) >> 6) {
			case 0:
				value = firstLengthByte;
				break;
			case 1:
				if (buffer.remaining() < 1) {
					throw new ParseException("Not enough bytes to parse long", 0);
				}
				buffer.position(buffer.position() - 1);
				value = buffer.getShort() & 0x3fff;
				break;
			case 2:
				if (buffer.remaining() < 3) {
					throw new ParseException("Not enough bytes to parse long", 0);
				}
				buffer.position(buffer.position() - 1);
				value = buffer.getInt() & 0x3fffffff;
				break;
			case 3:
				if (buffer.remaining() < 7) {
					throw new ParseException("Not enough bytes to parse long", 0);
				}
				buffer.position(buffer.position() - 1);
				value = buffer.getLong() & 0x3fffffffffffffffL;
				break;
			default:
				throw new RuntimeException();
		}
		return value;
	}

	private int parse(ByteBuffer data) throws ParseException {
		long value = parseLong(data);
		if (value <= Integer.MAX_VALUE) {
			return (int) value;
		} else {
			throw new IllegalArgumentException("value to large for Java int");
		}
	}

	private List<QuicFrame> parsePacketNumberAndPayload(ByteBuffer data, byte flags, Integer remainingLength, Map<String,byte[]> ckeys, long largestPacketNumber) throws ParseException {
		if (data.remaining() < remainingLength) {
			throw new ParseException("Not enough bytes to parse packet", 0);
		}

		int currentPosition = data.position();
		// 以假定的4个字节来解析packet number
		if (data.remaining() < 4) {
			throw new ParseException("Not enough bytes to parse packet number", 0);
		}

		data.position(currentPosition + 4);

		if (data.remaining() < 16) {
			throw new ParseException("Not enough bytes to parse payload", 0);
		}

		byte[] sample = new byte[16];
		data.get(sample);
		String hex = new BigInteger(1, sample).toString(16);

		String sample_hex = new BigInteger(1, sample).toString(16);
		byte[] mask = createHeaderProtectionMask(sample, ckeys);

		String mask_hex = new BigInteger(1, mask).toString(16);
		byte decryptedFlags;
		if ((flags & 0x80) == 0x80) {
			// Long header: 4 bits masked
			decryptedFlags = (byte) (flags ^ mask[0] & 0x0f);
		}

		else {
			// Short header: 5 bits masked
			decryptedFlags = (byte) (flags ^ mask[0] & 0x1f);
		}
		data.position(currentPosition);

		byte[] tmp = new byte[1];
		tmp[0] = decryptedFlags;
		String decryptedFlags_hex =  new BigInteger(1, tmp).toString(16);
		// packet number
		int protectedPackageNumberLength = (decryptedFlags & 0x03) + 1;
		byte[] protectedPackageNumber = new byte[protectedPackageNumberLength];
		data.get(protectedPackageNumber);

		byte[] unprotectedPacketNumber = new byte[protectedPackageNumberLength];
		for (int i = 0; i < protectedPackageNumberLength; i++) {
			// https://tools.ietf.org/html/draft-ietf-quic-tls-17#section-5.4.1:
			// " ...and the packet number is
			//   masked with the remaining bytes.  Any unused bytes of mask that might
			//   result from a shorter packet number encoding are unused."
			unprotectedPacketNumber[i] = (byte) (protectedPackageNumber[i] ^ mask[1+i]);
		}

		long packetNumber = bytesToInt(unprotectedPacketNumber);
		packetNumber = decodePacketNumber(packetNumber, largestPacketNumber, protectedPackageNumberLength * 8);

		currentPosition = data.position();
		byte[] frameHeader = new byte[data.position()];
		String frameHeader_hex =  new BigInteger(1, frameHeader).toString(16);
		data.position(0);
		data.get(frameHeader);
		frameHeader[0] = decryptedFlags;
		data.position(currentPosition);

		// Copy unprotected (decrypted) packet number in frame header, before decrypting payload.
		System.arraycopy(unprotectedPacketNumber, 0, frameHeader, frameHeader.length - (protectedPackageNumberLength), protectedPackageNumberLength);

		// "The input plaintext, P, for the AEAD is the payload of the QUIC
		//   packet, as described in [QUIC-TRANSPORT]."
		// "The output ciphertext, C, of the AEAD is transmitted in place of P."
		int encryptedPayloadLength = remainingLength - protectedPackageNumberLength;
		if (encryptedPayloadLength < 1) {
			throw new ParseException("Not enough bytes to parse payload", 0);
		}
		byte[] payload = new byte[encryptedPayloadLength];
		data.get(payload, 0, encryptedPayloadLength);

		String payload_hex =  new BigInteger(1, payload).toString(16);

		frameHeader_hex =  new BigInteger(1, frameHeader).toString(16);
		byte[] frameBytes = decryptPayload(payload, frameHeader, packetNumber, ckeys);
		String frame_hex = new BigInteger(1, frameBytes).toString(16);
		List<QuicFrame> frames = new ArrayList<>();
		parseFrames(frames, frameBytes);

		return frames;
	}

	private void parseFrames(List<QuicFrame> frames, byte[] frameBytes) throws ParseException{
		ByteBuffer buffer = ByteBuffer.wrap(frameBytes);
		int frameType = -1;
		while (buffer.remaining() > 0) {
			// https://tools.ietf.org/html/draft-ietf-quic-transport-16#section-12.4
			// "Each frame begins with a Frame Type, indicating its type, followed by additional type-dependent fields"
			buffer.mark();
			frameType = buffer.get();
			buffer.reset();
			switch (frameType) {
				case 0x00:
					frames.add(new Padding().parse(buffer));
					break;
				case 0x01:
					frames.add(new PingFrame().parse(buffer));
					break;
				case 0x06:
					frames.add(new CryptoFrame().parse(buffer));
					break;
			}
		}
	}


	private byte[] createHeaderProtectionMask(byte[] ciphertext, Map<String,byte[]> ckeys) throws ParseException{
		int sampleOffset = 0;
		byte[] sample = new byte[16];
		System.arraycopy(ciphertext, sampleOffset, sample, 0, 16);

		Cipher hpCipher = getHeaderProtectionCipher(ckeys);
		byte[] mask;
		try {
			mask = hpCipher.doFinal(sample);
		} catch (IllegalBlockSizeException | BadPaddingException e) {
			// Programming error
			throw new RuntimeException();
		}
		return mask;
	}

	public Cipher getHeaderProtectionCipher(Map<String,byte[]> ckeys) throws ParseException{
		Cipher hpCipher = null;
		byte[] hp = ckeys.get("hp");
		try {
			// https://tools.ietf.org/html/draft-ietf-quic-tls-27#section-5.4.3
			// "AEAD_AES_128_GCM and AEAD_AES_128_CCM use 128-bit AES [AES] in electronic code-book (ECB) mode."
			hpCipher = Cipher.getInstance("AES/ECB/NoPadding");
			SecretKeySpec keySpec = new SecretKeySpec(hp, "AES");
			hpCipher.init(Cipher.ENCRYPT_MODE, keySpec);
		} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
			// Inappropriate runtime environment
			throw new ParseException("Inappropriate runtime environment", 0);
		} catch (InvalidKeyException e) {
			// Programming error
			throw new RuntimeException();
		}
		return hpCipher;
	}

	private byte[] hkdfExpandLabel(byte[] secret, String label, String context, short length) {
		Charset ISO_8859_1 = Charset.forName("ISO-8859-1");

		byte[] prefix;
		// https://tools.ietf.org/html/draft-ietf-quic-tls-17#section-5.1:
		// "The keys used for packet protection are computed from the TLS secrets using the KDF provided by TLS."
		prefix = "tls13 ".getBytes(ISO_8859_1);

		ByteBuffer hkdfLabel = ByteBuffer.allocate(2 + 1 + prefix.length + label.getBytes(ISO_8859_1).length + 1 + context.getBytes(ISO_8859_1).length);
		hkdfLabel.putShort(length);
		hkdfLabel.put((byte) (prefix.length + label.getBytes().length));
		hkdfLabel.put(prefix);
		hkdfLabel.put(label.getBytes(ISO_8859_1));
		hkdfLabel.put((byte) (context.getBytes(ISO_8859_1).length));
		hkdfLabel.put(context.getBytes(ISO_8859_1));
		HKDF hkdf = HKDF.fromHmacSha256();
		return hkdf.expand(secret, hkdfLabel.array(), length);
	}


	private Map<String, byte[]> computeKeys(byte[] secret) {
		Map<String, byte[]> keys = new HashMap<>();

		String prefix = "quic ";

		// https://tools.ietf.org/html/rfc8446#section-7.3
		byte[] key = hkdfExpandLabel(secret, prefix + "key", "", (short) 16);
		String hex = new BigInteger(1, key).toString(16);
		keys.put("writeKey", key);
		keys.put("writeKeySpec", null);

		byte[] iv = hkdfExpandLabel(secret, prefix + "iv", "", (short) 12);
		String hex2 = new BigInteger(1, iv).toString(16);
		keys.put("iv", iv);

		byte[] hp = hkdfExpandLabel(secret, prefix + "hp", "", (short) 16);
		String hex3 = new BigInteger(1, hp).toString(16);
		keys.put("hp", hp);
		return keys;
	}

	static int bytesToInt(byte[] data) {
		int value = 0;
		for (int i = 0; i < data.length; i++) {
			value = (value << 8) | (data[i] & 0xff);

		}
		return value;
	}

	static long decodePacketNumber(long truncatedPacketNumber, long largestPacketNumber, int bits) {
		long expectedPacketNumber = largestPacketNumber + 1;
		long pnWindow = 1L << bits;
		long pnHalfWindow = pnWindow / 2;
		long pnMask = ~ (pnWindow - 1);

		long candidatePn = (expectedPacketNumber & pnMask) | truncatedPacketNumber;
		if (candidatePn <= expectedPacketNumber - pnHalfWindow && candidatePn < (1 << 62) - pnWindow) {
			return candidatePn + pnWindow;
		}
		if (candidatePn > expectedPacketNumber + pnHalfWindow && candidatePn >= pnWindow) {
			return candidatePn - pnWindow;
		}

		return candidatePn;
	}

	byte[] decryptPayload(byte[] message, byte[] associatedData, long packetNumber, Map<String,byte[]> secrets) throws ParseException {
		ByteBuffer nonceInput = ByteBuffer.allocate(12);
		nonceInput.putInt(0);
		nonceInput.putLong(packetNumber);

		byte[] writeIV = secrets.get("iv");
		String iv_hex = new BigInteger(1, writeIV).toString(16);
		byte[] nonce = new byte[12];
		int i = 0;
		for (byte b : nonceInput.array())
			nonce[i] = (byte) (b ^ writeIV[i++]);
		String nonce_hex = new BigInteger(1, nonce).toString(16);
		return aeadDecrypt(associatedData, message, nonce, secrets.get("writeKey"));
	}

	public byte[] aeadDecrypt(byte[] associatedData, byte[] message, byte[] nonce, byte[] writekey) throws ParseException{
		if (message.length <= 16) {
			// https://www.rfc-editor.org/rfc/rfc9001.html#name-aead-usage
			// "These cipher suites have a 16-byte authentication tag and produce an output 16 bytes larger than their input."
			throw new ParseException("ciphertext must be longer than 16 bytes", 0);
		}
		SecretKeySpec secretKey = getWriteKeySpec(writekey);
		try {
			Cipher aeadCipher = getWriteCipher();
			GCMParameterSpec parameterSpec = new GCMParameterSpec(128, nonce);   // https://tools.ietf.org/html/rfc5116#section-5.3: "the tag length t is 16"
			aeadCipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
			aeadCipher.updateAAD(associatedData);
			return aeadCipher.doFinal(message);
		} catch (AEADBadTagException decryptError) {
			throw new ParseException("decryption failed", 0);
		} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
			// Programming error
			throw new RuntimeException();
		} catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
			// Programming error
			throw new RuntimeException();
		}
	}

	public SecretKeySpec getWriteKeySpec(byte[] writekey) {
		SecretKeySpec writeKeySpec = new SecretKeySpec(writekey, "AES");
		return writeKeySpec;
	}

	public Cipher getWriteCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
		Cipher writeCipher = null;
		if (writeCipher == null) {
			// From https://tools.ietf.org/html/draft-ietf-quic-tls-16#section-5.3:
			// "Prior to establishing a shared secret, packets are protected with AEAD_AES_128_GCM"
			String AES_GCM_NOPADDING = "AES/GCM/NoPadding";
			writeCipher = Cipher.getInstance(AES_GCM_NOPADDING);
		}
		return writeCipher;
	}

	public static String printHexBinary(byte[] data) {
		char[] hexCode = "0123456789ABCDEF".toCharArray();
		StringBuilder r = new StringBuilder(data.length * 2);
		for (byte b : data) {
			r.append(hexCode[(b >> 4) & 0xF]);
			r.append(hexCode[(b & 0xF)]);
			r.append(",");
		}
		return r.toString();
	}
}